package com.jctf.net;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;

public class SequenceServer2 implements MessageListener {

	private final static int PORT = 33962;
	private final static int MAX_CLIENTS = 10;

	private HashMap<String, String> loginData = new HashMap<String, String>();
	private HashMap<Integer, PlayerData> players = new HashMap<Integer, PlayerData>();
	private NetworkManager manager;
	private MessageDigest encryptor;

	public static void main(String[] args) {
		new SequenceServer2().run();
	}

	public void run() {
		System.out.print("Setting up login data...");
		readLoginData();
		try {
			encryptor = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		}
		System.out.println("done.");
		System.out.print("Setting up server...");
		try {
			initServer();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("Quitting.");
	}

	private void readLoginData() {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(new File(SequenceServer2.class.getResource("data/logindata.txt").toURI())));
			ArrayList<String> sets = new ArrayList<String>();
			String temp = reader.readLine();
			while(temp != null) {
				sets.add(temp);
				temp = reader.readLine();
			}
			for(String s : sets) {
				String[] a = s.split(" ");
				loginData.put(a[0], a[1]);
			}
		} catch (FileNotFoundException e) {
			System.out.println("Error - could not find login data! Server aborting.");
			e.printStackTrace();
			System.exit(1);
		} catch (IOException e) {
			System.out.println("I/O error - Server aborting.");
			e.printStackTrace();
			System.exit(1);
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void messageReceived(Message message) {

	}

	private void initServer() throws IOException {
		manager = new NetworkManager();
		manager.registerMessageListener(this);
		System.out.println("done.");
		System.out.println("Waiting for connections...");
		manager.start();
	}

	private class NetworkManager extends NetworkHandler {

		private ServerSocket server;
		private ArrayList<Socket> connections = new ArrayList<Socket>();
		private ArrayList<ObjectInputStream> inputs = new ArrayList<ObjectInputStream>();
		private ArrayList<ObjectOutputStream> outputs = new ArrayList<ObjectOutputStream>();
		private int nextId = 0;

		@Override
		public void registerMessageListener(MessageListener l) {
			listeners.add(l);
		}

		public void start() throws IOException {
			server = new ServerSocket(PORT, MAX_CLIENTS);
			new HermesLoginManager().start();
			new HermesGameDataManager().start();
			acceptNewConnections();
		}

		private void acceptNewConnections() throws IOException {
			while(true) {
				Socket connection = server.accept();
				synchronized(connection) {
					connections.add(connection);
					inputs.add(new ObjectInputStream(connection.getInputStream()));
					outputs.add(new ObjectOutputStream(connection.getOutputStream()));
					System.out.println("Received connection from " + connection.getInetAddress().getHostAddress());
				}
			}
		}

		private class HermesLoginManager extends Thread {

			@Override
			public void run() {
				setName("LoginManager");
				while(true) {
					for(int i = 0; i < connections.size(); i++) {
						try {
							Socket c = connections.get(i);
							if(inputs.size() == 0 || outputs.size() == 0 || connections.size() == 0)
								continue;
							ObjectInputStream in = inputs.get(i);
							ObjectOutputStream out = outputs.get(i);
							Object message = inputs.get(i).readObject();
							if(message == null)
								continue;
							else if(message instanceof RequestLoginMessage) {
								System.out.println("Login request received from " + c.getInetAddress().getHostAddress());
								RequestLoginMessage m = (RequestLoginMessage) message;
								String toHash = m.getUsername() + " " + m.getPassword();
								encryptor.update(toHash.getBytes(), 0, toHash.length());
								String hash = new BigInteger(1, encryptor.digest()).toString(16);
								if(hash.equals(loginData.get(m.getUsername()))) {
									players.put(nextId, new PlayerData(c, in, out));
									System.out.println("Authentication complete - client " + c.getInetAddress().getHostAddress() + " assigned id " + nextId + ". Responding...");
									nextId++;
									out.writeObject(new ConfirmLoginMessage());
									out.flush();
									connections.remove(i);
									outputs.remove(i);
									inputs.remove(i);
									i--;
									continue;
								}
								else {
									System.out.println("Authentication failed with client " + c.getInetAddress().getHostAddress());
									out.writeObject(new DenyLoginMessage());
									out.flush();
									connections.remove(i);
									outputs.remove(i);
									inputs.remove(i);
									i--;
								}
							}
						} catch (IOException e) {
							e.printStackTrace();
						} catch (ClassNotFoundException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
		
		private class HermesGameDataManager extends Thread {
			
			@Override
			public void run() {
				setName("GameDataManager");
				while(true) {
					for(int i = 0; i < players.keySet().size(); i++) {
						try {
						PlayerData player = players.get(i);
						//Socket c = player.getConnection();
						//ObjectOutputStream out = player.getOutput();
						ObjectInputStream in = player.getInput();
						Object message = in.readObject();
						if(message == null)
							continue;
						else if(message instanceof NumberMessage)
							System.out.println("Client " + i + ": " + ((NumberMessage) message).getNumber());
						} catch (EOFException e) {
							System.out.println("Client " + i + " exit.");
							players.remove(i);
						} catch (IOException e) {
							e.printStackTrace();
						} catch (ClassNotFoundException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	}
}
